package com.zhou.hot.agent;


import com.zhou.hot.util.HotResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;

public class HotLoadAgent
{
  static HotLoadConfig CONFIG;

  public static SimpleDateFormat fmt = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
  public static Logger logger = LoggerFactory.getLogger("hot.load");

    /**
     * 实现重定义Class配置
     * @param args
     * @param inst
     */
  public static void agentmain(String args, Instrumentation inst) {
    try {
      initConfig();
    } catch (Exception e) {
      logger.error("CONFIG LOAD ERROR",e);
      return;
    }
    logger.info("hot load agent start....classesDir="+CONFIG.getClassesDir()+",\n classesConfig="+CONFIG.getClassesConfig());
    File dir = new File(CONFIG.getClassesDir());
    Properties properties = CONFIG.loadClassesConfigProperties();
  //LOAD
   HotResult hotResult = loadClassDefinitions(inst,dir.listFiles(),properties);
    if (hotResult.isFail()) {
        return;
    }
    List<ClassDefinition> definitions = hotResult.getDefinitions();
    List<File> classFiles = hotResult.getFiles();
    //重定义class
    hotResult = redefineClasses(inst,definitions);
    //报错到历史文件中
    if (hotResult.isSucc()) {
      moveToHistory(classFiles);
    }
    logger.info("hot load agent end...");

  }

  public static void moveToHistory(List<File> classFiles) {
      String path = CONFIG.getClassesHistory()+"/"+fmt.format(new Date());
      try {
          logger.info("hot load agent  move to history["+path+"]");
          File historyDir = new File(path);
          if (!historyDir.exists()) {
              historyDir.mkdirs();
          }
          for (File clazzFile : classFiles) {
            boolean moveSucc = clazzFile.renameTo(new File(historyDir.getAbsolutePath(), clazzFile.getName()));
            if (!moveSucc) {
              logger.info("hot load agent " + clazzFile.getName() + "move to history is fail,please move youself!");
            }
          }
      } catch (Exception e) {
          logger.error("hot load agent  move to history["+path+"] fail!",e);
      }
  }
    /**
     
     * 此方法用于在不引用现有类文件字节的情况下替换类的定义，就像从源代码重新编译以进行修复并继续调试时所做的那样。
     *
     * 将转换现有类文件字节的位置（例如在字节码检测中）
     * 
     * 此方法对集合进行操作，以便允许同时对多个类进行相互依赖的更改（重新定义a类可能需要重新定义B类）
     
     * 如果重新定义的方法具有活动堆栈帧，则这些活动帧将继续运行原始方法的字节码。他重新定义的方法将用于新的调用。
     
     * 此方法不会导致任何初始化，除非是在常规JVM语义下发生的初始化。换句话说，重新定义一个类不会导致它的初始值设定项被运行。
     * 静态变量的值将保持调用之前的值。
     
     * 重新定义的类实例不受影响
     
     * 重新定义可能改变方法体、常量池和属性（除非明确禁止的）

     * 重新定义不能增加，删除、重命名字段或者方法，改变方法签名或者更改继承

     * 重定义不能改变NestHost或NestMembers属性。

     * 这些限制将来的版本可能会取消
     
     * 在应用转换之前，不会检查、验证和安装类文件字节，如果结果字节出错，此方法将引发异常。
     {@linkplain Instrumentation#redefineClasses(ClassDefinition...)}
     */
  private static HotResult redefineClasses(Instrumentation inst, List<ClassDefinition> definitions) {
    try {
      ClassDefinition[] arr = new ClassDefinition[definitions.size()];
      definitions.toArray(arr);
      inst.redefineClasses(arr);
      /*System.out.printf("已替换%d个class...\n", new Object[] { Integer.valueOf(arr.length) });*/
      logger.info("hot load agent " + " change " + arr.length + " num class file...");
      return HotResult.RESULT_SUCC;
    } catch (Exception e) {
      logger.error("hot load agent 替换失败", e);
    }
    return HotResult.RESULT_FAIL;
  }

  private static HotResult loadClassDefinitions(Instrumentation inst, File[] listFiles, Properties properties) {
      if (listFiles == null || listFiles.length <= 0) {
          logger.error("hot load agent " + CONFIG.getClassesConfig()+"class file size=0",new Exception());
          return HotResult.createFailResult("class file size = 0");
      }
      List<ClassLoader> classLoaders = getClassLoaders(inst);
    List<ClassDefinition> definitions = new ArrayList<>();
    List<File> classFiles = new ArrayList<>();
    int classFileSize = 0;
    for (File clazzFile : listFiles) {
      if (!clazzFile.getName().endsWith(".class")){
          continue;
      }
      String className = checkClassFilePackage(clazzFile,properties);
      if (className == null) {
        continue;
      }
        byte[] bytes = fileToBytes(clazzFile);
        if (bytes == null) {
            logger.error(clazzFile.getName() + "file error");
            return HotResult.RESULT_FAIL;
        }
        classFiles.add(clazzFile);
        classFileSize ++;
        for (ClassLoader classLoader : classLoaders) {
            Class clazz = null;
            try {
                clazz = Class.forName(className,true,classLoader);
            } catch (ClassNotFoundException e) {
                clazz = null;
                logger.error("hot load agent " + clazzFile.getName() + "setting error,value:" + className,e);
            }
            if (clazz == null) {
//        System.out.printf("%s配置出错,value:%s\n", new Object[] { clazzFile.getName(), className });
                logger.info("hot load agent " + clazzFile.getName() + "setting error,value:" + className);
                return HotResult.RESULT_FAIL;
            }
            logger.info("hot load agent " + clazzFile.getName() + "file bytes=" + bytes.length);
            ClassDefinition classDefinition = new ClassDefinition(clazz, bytes);
            definitions.add(classDefinition);
        }
//      System.out.printf("%s文件大小:%d\n", new Object[] { clazzFile.getName(), Integer.valueOf(bytes.length) });
    }

    return HotResult.createSuccResult(definitions,classFiles);
  }

  /*  private static ClassLoader getClassLoader() {
      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        return classLoader;
    }*/

    private static List<ClassLoader> getClassLoaders(Instrumentation inst) {
        List<ClassLoader> loaders = new ArrayList<>();
        if (CONFIG.getClassLoderNames().length > 0) {
            Class<?>[] allLoadedClasses = inst.getAllLoadedClasses();
            try {
                for (Class<?> clazz : allLoadedClasses) {
                    String className = clazz.getName();
                    if (className == null || className.isEmpty()) {
                        continue;
                    }
                    for (String classLoderName : CONFIG.getClassLoderNames()) {
                        if (classLoderName == null || classLoderName.length() == 0) {
                            continue;
                        }
                        if (classLoderName.equals(className)) {
                            ClassLoader classLoader = clazz.getClassLoader();
                            loaders.add(classLoader);
                            logger.info("[hot swap] find COKExtension ClassLoader {}", classLoader);
                        }
                    }
                }
            } catch (Throwable e1) {
                logger.error("",e1);
            }
        }

        if (loaders.isEmpty()) {
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            loaders.add(classLoader);
        }
        return loaders;
     /* ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        try {
//            Class<?> clazz = Class.forName("com.mmobuilder.server.COKServer");
            Class<?>  extensionClass = COKServer.getInstance().getExtensionManager().getClass();

            for (Field field : extensionClass.getFields()) {
                logger.info("field  ::: " + field);
            }
            Field field = extensionClass.getDeclaredField("zoneExtensions");

            field.setAccessible(true);
            Map map = (Map)field.get(COKServer.getInstance().getExtensionManager());
            classLoader = map.values().iterator().next().getClass().getClassLoader();

        } catch (Exception e) {
            e.printStackTrace();
        }
        logger.info("hot load agent cok classLoader " + classLoader);
        return classLoader;*/
    }

    /**
     * 加载配置
     * @throws Exception
     */

  public static void initConfig() throws Exception {
    CONFIG = new HotLoadConfig();
    CONFIG.load();
  }


  public static byte[] fileToBytes(File file) {
      FileInputStream in = null;
      ByteArrayOutputStream baos = null;
      try {
      in = new FileInputStream(file);
      baos = new ByteArrayOutputStream();
      byte[] buffer = new byte[1024];
      int readSize = -1;
      while ((readSize = in.read(buffer)) != -1) {
        baos.write(buffer, 0, readSize);
      }

      baos.flush();

      return baos.toByteArray();
    } catch (IOException e) {
      logger.error("hot load agent file:" + file.getName() + " read error",e);
        return null;
    }finally {
          try {
              if (in != null) {
                  in.close();
              }
              if (baos != null ) {
                  baos.close();
              }
          } catch (IOException e) {
          }
      }

  }

  public static String checkClassFilePackage(File file,Properties properties)
  {
      String packageClassName = null;
      if (properties != null) {
          packageClassName = properties.getProperty(file.getName());
      }
      if (packageClassName == null)
      {
          packageClassName = javapClassFile(file);
      }
      logger.info("hot load agent packageClassName:"+packageClassName );
      return packageClassName;
  }

    public static void main(String[] args) {

        File file = new File("F:\\projects\\online_project\\slg\\ClashOfKingProject\\SFS2X\\hot\\classes\\TelnetConsoleServiceImpl.class");
        String className = javapClassFile(file);
        System.out.println(className);
    }

    public static boolean isWindows() {
        return System.getProperties().getProperty("os.name").toUpperCase().indexOf("WINDOWS") != -1;
    }
    /**
     * 通过javam命令直接从class文件中获取全包类
     * @param file
     * @return
     */
  public static String javapClassFile(File file)
  {
      String packageName = null;
      BufferedReader bufferedReader = null;
      Process process = null;
      try {

          String name = file.getName().substring(0,file.getName().lastIndexOf("."));
          Runtime run = Runtime.getRuntime();
          String JDK = System.getenv("JAVA_HOME");
          String sysSep = System.getProperty("file.separator");
          String jdkBin = JDK+sysSep+"bin"+sysSep;
          jdkBin = jdkBin.replaceAll("\"","");
          logger.info("hot load agent javapClassFile jdkBin :"+ jdkBin);
          //可打开exe程序，也可执行cmd命令，注意 路径\要使用\\，表示转义
          // Process p = run.exec("E:\\一卡通测试工具\\串口调试助手\\kComm.exe");  //执行exe程序
          String cmd = jdkBin+"javap "+file.getAbsolutePath();
          if (isWindows()) {
              cmd = cmd.replaceAll("\\\\", "/");
          }
          logger.info("hot load agent javapClassFile cmd :"+ cmd);
          process = run.exec(cmd);                  //执行CMD命令
          bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.forName("UTF-8")));
          logger.info("hot load agent javapClassFile cmd :"+ cmd);
          List<String> list = new ArrayList<>();
          String lineMes;
          int size =0;
          aa : while ((lineMes = bufferedReader.readLine()) != null)
          {
//              System.out.println(lineMes);// 打印输出信息
              size ++;
              if (size == 2)
              {
                  String[] array = lineMes.trim().split(" ");
                  for (int i=0;i<array.length;i++)
                  {
                      if (array[i].equalsIgnoreCase("interface") || array[i].equalsIgnoreCase("class") || array[i].equalsIgnoreCase("enum"))
                      {
                          String packageClassName = array[i+1];
                          if (packageClassName.endsWith(name))
                          {
                              packageName = packageClassName;
//                              System.err.println(packageClassName);
                              logger.info("hot load agent  read class file["+file.getName()+"] package = "+packageClassName);
                              break aa;
                          }

                      }
                  }
                  break;
              }
          }
          process.waitFor(5, TimeUnit.SECONDS);
          //检查命令是否执行失败。
//          if (process.waitFor() != 0) {
//              if (process.exitValue() == 1){
//                  //0表示正常结束，1：非正常结束
//                  logger.error("hot load agent cmd exec fail,exitValue="+process.exitValue());
//                  throw new Exception("cmd exec fail...");
//              }
//          }

      } catch (Exception e) {
          logger.error("",e);
      }finally {
          try {
              if(bufferedReader != null) {
                  bufferedReader.close();
              }
          } catch (IOException e) {
          }
      }
      return packageName;
  }

}